import clsx from 'clsx';
import React, { useCallback, useEffect, useMemo, useRef } from 'react';

export type RecordObject = Record<string, any>;

export interface Column<RecordType = RecordObject> {
  title: string;
  dataIndex?: keyof RecordType | string;
  key?: string;
  render?: (value: any, record: RecordType, index: number) => React.ReactNode;
  width?: string | number;
  align?: 'left' | 'center' | 'right';
  fixed?: boolean; // 是否固定列
  className?: string;
}

export interface TableProps<RecordType = RecordObject> {
  className?: string;
  style?: React.CSSProperties;
  tableClassName?: string;
  rowKey?: string | ((record: RecordType) => string | number);
  loading?: boolean;
  columns?: Column<RecordType>[];
  dataSource?: RecordType[];
  /**
   * 是否固定表头，固定表头需要通过 className 或 style 给组件设置高度，否则不生效
   */
  pinnedHeader?: boolean;
  rowSelectionColumn?: {
    fixed?: boolean; // 是否固定选择列
    align?: 'left' | 'center' | 'right'; // 对齐方式
    width?: string | number;
    className?: string;
    selectedRowKeys: string[];
    onChange: (selectedRowKeys: string[]) => void;
  };
  rowProps?: (record: RecordType) => React.HTMLAttributes<HTMLTableRowElement>;
}

function TableNoMemoized<RecordType = RecordObject>(props: TableProps<RecordType>) {
  const {
    className,
    tableClassName,
    rowKey = 'key',
    loading,
    columns,
    dataSource,
    pinnedHeader,
    rowSelectionColumn,
    rowProps,
  } = props;

  // Refs
  const headerCheckboxRef = useRef<HTMLInputElement>(null);

  // Handlers
  const getKey = useCallback(
    (item: RecordType) => {
      return typeof rowKey === 'function' ? rowKey(item) : item[rowKey as keyof RecordType];
    },
    [rowKey]
  );

  // Memoized
  const allChecked = useMemo(
    () =>
      dataSource?.every((item) => {
        const key = getKey(item);
        return rowSelectionColumn?.selectedRowKeys.includes(key as string);
      }),
    [dataSource, getKey, rowSelectionColumn?.selectedRowKeys]
  );

  useEffect(() => {
    if (headerCheckboxRef.current && rowSelectionColumn) {
      const { selectedRowKeys } = rowSelectionColumn;
      headerCheckboxRef.current.indeterminate = Boolean(
        !allChecked && selectedRowKeys && selectedRowKeys.length > 0
      );
    }
  }, [allChecked, rowSelectionColumn]);

  return (
    <div className={clsx('overflow-x-auto relative w-full', className)}>
      {loading && (
        <div className="absolute inset-0 grid place-items-center bg-base-100/60 z-10">
          <span className="loading loading-spinner loading-md" />
        </div>
      )}
      <table
        className={clsx('table table-pin-cols', pinnedHeader && 'table-pin-rows', tableClassName)}
      >
        <thead>
          <tr className="z-50">
            {rowSelectionColumn && (
              <th
                style={{ width: rowSelectionColumn.width }}
                className={clsx(
                  rowSelectionColumn.align === 'center' && 'text-center',
                  rowSelectionColumn.align === 'left' && 'text-left',
                  rowSelectionColumn.align === 'right' && 'text-right',
                  rowSelectionColumn.className
                )}
              >
                <label>
                  <input
                    ref={headerCheckboxRef}
                    type="checkbox"
                    className="checkbox"
                    checked={allChecked}
                    onChange={(e) => {
                      if (e.target.checked) {
                        // 全选
                        const allKeys =
                          dataSource?.map((item) => {
                            const key = getKey(item);
                            return key as string;
                          }) ?? [];
                        rowSelectionColumn.onChange(allKeys);
                      } else {
                        // 全不选
                        rowSelectionColumn.onChange([]);
                      }
                    }}
                  />
                </label>
              </th>
            )}
            {columns?.map((column, index) => (
              <th
                key={column.key ?? column.dataIndex?.toString() ?? index}
                style={{ width: column.width }}
                className={clsx(
                  column.align === 'left' && 'text-left',
                  column.align === 'center' && 'text-center',
                  column.align === 'right' && 'text-right',
                  column.className
                )}
              >
                {column.title}
              </th>
            ))}
          </tr>
        </thead>
        <tbody>
          {dataSource?.map((item) => {
            const key = getKey(item) as string;
            const { className: rowClassName, ...restRowProps } = rowProps ? rowProps(item) : {};
            const RowSelectionCol = rowSelectionColumn?.fixed ? 'th' : 'td';
            return (
              <tr key={key} {...restRowProps} className={clsx('hover:bg-base-300', rowClassName)}>
                {rowSelectionColumn && (
                  <RowSelectionCol
                    className={clsx(
                      rowSelectionColumn.align === 'center' && 'text-center',
                      rowSelectionColumn.align === 'left' && 'text-left',
                      rowSelectionColumn.align === 'right' && 'text-right',
                      rowSelectionColumn.fixed && 'z-10',
                      rowSelectionColumn.className
                    )}
                    onClick={(e) => e.stopPropagation()}
                    onMouseDown={(e) => e.stopPropagation()}
                  >
                    <label>
                      <input
                        type="checkbox"
                        className="checkbox"
                        checked={rowSelectionColumn.selectedRowKeys.includes(key)}
                        onChange={(e) => {
                          const nextSelectedKeys = rowSelectionColumn.selectedRowKeys.filter(
                            (k) => k !== key
                          );
                          if (e.target.checked) {
                            nextSelectedKeys.push(key);
                          }
                          rowSelectionColumn.onChange(nextSelectedKeys);
                        }}
                      />
                    </label>
                  </RowSelectionCol>
                )}
                {columns?.map((column, index) => {
                  const value = item[column.dataIndex as keyof RecordType];
                  const content = column.render ? column.render(value, item, index) : value;
                  const Col = column?.fixed ? 'th' : 'td';
                  return (
                    <Col
                      key={column.key ?? column.dataIndex?.toString() ?? index}
                      className={clsx(
                        column.align === 'left' && 'text-left',
                        column.align === 'center' && 'text-center',
                        column.align === 'right' && 'text-right',
                        column.fixed && 'z-50',
                        column.className
                      )}
                    >
                      {content as React.ReactNode}
                    </Col>
                  );
                })}
              </tr>
            );
          })}
        </tbody>
      </table>
    </div>
  );
}

export const Table = React.memo(TableNoMemoized) as typeof TableNoMemoized;
